//
// Created by Administrator on 2025/3/16.
//

#include "android/log.h"

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <jni.h>
#include <unordered_map>

#define LOG_TAG "SerialPort"
#define LOG_DEBUG(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)

static speed_t baud_rate_converter(jint baud_rate) {
    if (baud_rate < 0) return B0;
    static const std::unordered_map<jint, speed_t> rate_map = {
            {0,       B0},
            {50,      B50},
            {75,      B75},
            {110,     B110},
            {134,     B134},
            {150,     B150},
            {200,     B200},
            {300,     B300},
            {600,     B600},
            {1200,    B1200},
            {1800,    B1800},
            {2400,    B2400},
            {4800,    B4800},
            {9600,    B9600},
            {19200,   B19200},
            {38400,   B38400},
            {57600,   B57600},
            {115200,  B115200},
            {230400,  B230400},
            {460800,  B460800},
            {500000,  B500000},
            {576000,  B576000},
            {921600,  B921600},
            {1000000, B1000000},
            {1152000, B1152000},
            {1500000, B1500000},
            {2000000, B2000000},
            {2500000, B2500000},
            {3000000, B3000000},
            {3500000, B3500000},
            {4000000, B4000000}
    };
    auto it = rate_map.find(baud_rate);
    return it != rate_map.end() ? it->second : (speed_t) -1;
}

extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_6;
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_pengxh_cmake_uart_SerialPort_open(JNIEnv *env, jobject thiz, jstring path, jint baud_rate,
                                           jint flags) {
    int fd;
    speed_t speed;

    // 检查参数
    speed = baud_rate_converter(baud_rate);
    if (speed == (speed_t) -1) {
        LOG_DEBUG("invalid baud_rate");
        return nullptr;
    }

    // 打开设备
    const char *path_utf = env->GetStringUTFChars(path, nullptr);
    if (path_utf == nullptr) {
        LOG_DEBUG("failed to get path UTF chars");
        return nullptr;
    }
    LOG_DEBUG("opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
    fd = open(path_utf, O_RDWR | flags);
    env->ReleaseStringUTFChars(path, path_utf);

    if (fd == -1) {
        LOG_DEBUG("open port failed: %s", strerror(errno));
        return nullptr;
    }

    // 配置设备
    struct termios cfg{};
    LOG_DEBUG("configuring serial port");
    if (tcgetattr(fd, &cfg)) {
        LOG_DEBUG("tcgetattr() failed: %s", strerror(errno));
        close(fd);
        return nullptr;
    }

    cfmakeraw(&cfg);
    cfsetispeed(&cfg, speed);
    cfsetospeed(&cfg, speed);

    if (tcsetattr(fd, TCSANOW, &cfg)) {
        LOG_DEBUG("tcsetattr() failed: %s", strerror(errno));
        close(fd);
        return nullptr;
    }

    jclass parcel_fd_class = env->FindClass("android/os/ParcelFileDescriptor");
    if (!parcel_fd_class) {
        LOG_DEBUG("Failed to find ParcelFileDescriptor class");
        close(fd);
        return nullptr;
    }

    jmethodID from_fd_method = env->GetStaticMethodID(parcel_fd_class, "fromFd",
                                                      "(I)Landroid/os/ParcelFileDescriptor;");
    if (!from_fd_method) {
        LOG_DEBUG("Failed to find ParcelFileDescriptor.fromFd method");
        close(fd);
        return nullptr;
    }

    jobject parcel_fd = env->CallStaticObjectMethod(parcel_fd_class, from_fd_method, fd);
    if (!parcel_fd) {
        LOG_DEBUG("Failed to create ParcelFileDescriptor from fd");
        close(fd);
        return nullptr;
    }

    return parcel_fd;
}

extern "C" JNIEXPORT void JNICALL
Java_com_pengxh_cmake_uart_SerialPort_close(JNIEnv *env, jobject thiz) {
    jclass serial_port_class = env->GetObjectClass(thiz);
    if (serial_port_class == nullptr) {
        LOG_DEBUG("failed to get SerialPort class");
        return;
    }

    jfieldID fd_field_id = env->GetFieldID(serial_port_class, "mFd",
                                           "Landroid/os/ParcelFileDescriptor;");
    if (fd_field_id == nullptr) {
        LOG_DEBUG("failed to get mFd field ID");
        return;
    }

    jobject fd_obj = env->GetObjectField(thiz, fd_field_id);
    if (fd_obj == nullptr) {
        LOG_DEBUG("fd object is null");
        return;
    }

    jclass parcel_fd_class = env->GetObjectClass(fd_obj);
    jmethodID get_fd_method = env->GetMethodID(parcel_fd_class, "getFd", "()I");
    if (!get_fd_method) {
        LOG_DEBUG("failed to get getFd() method");
        return;
    }

    jint descriptor = env->CallIntMethod(fd_obj, get_fd_method);
    if (descriptor == -1) {
        LOG_DEBUG("invalid descriptor value");
        return;
    }

    LOG_DEBUG("closing file descriptor %d", descriptor);
    close(descriptor);

    jmethodID close_method = env->GetMethodID(parcel_fd_class, "close", "()V");
    if (close_method) {
        env->CallVoidMethod(fd_obj, close_method);
    }
}